<?php

namespace kitego\utils;

use Closure;
use think\Request;
use think\Response;

class HandleCorsUtil
{
    // 跨域配置参数（从配置文件读取）
    protected array $paths = [];
    protected array $allowedOrigins = [];
    protected array $allowedOriginsPatterns = [];
    protected array $allowedMethods = [];
    protected array $allowedHeaders = [];
    protected array $exposedHeaders = [];
    protected bool $supportsCredentials = false;
    protected ?int $maxAge = 0;

    // 快速判断标识（是否允许所有源/方法/头）
    protected bool $allowAllOrigins = false;
    protected bool $allowAllMethods = false;
    protected bool $allowAllHeaders = false;

    public function __construct()
    {
        // 读取跨域配置（假设配置文件路径正确）
        $options = config('cross');

        // 初始化基础配置
        $this->paths = $options['paths'] ?? [];
        $this->allowedOrigins = $options['allowed_origins'] ?? [];
        $this->allowedOriginsPatterns = $options['allowed_origins_patterns'] ?? [];
        $this->allowedMethods = $options['allowed_methods'] ?? [];
        $this->allowedHeaders = $options['allowed_headers'] ?? [];
        $this->exposedHeaders = $options['exposed_headers'] ?? [];
        $this->supportsCredentials = $options['supports_credentials'] ?? false;
        $this->maxAge = isset($options['max_age']) ? (int)$options['max_age'] : 0;

        // 标准化配置（统一大小写）
        $this->allowedHeaders = array_map('strtolower', $this->allowedHeaders);
        $this->allowedMethods = array_map('strtoupper', $this->allowedMethods);

        // 设置"允许所有"的标识（简化后续判断）
        $this->allowAllOrigins = in_array('*', $this->allowedOrigins);
        $this->allowAllMethods = in_array('*', $this->allowedMethods);
        $this->allowAllHeaders = in_array('*', $this->allowedHeaders);

        // 处理带通配符的源（转为正则）
        if (!$this->allowAllOrigins) {
            foreach ($this->allowedOrigins as $origin) {
                if (strpos($origin, '*') !== false) {
                    $this->allowedOriginsPatterns[] = $this->wildcardToRegex($origin);
                }
            }
        }
    }

    /**
     * 处理请求
     */
    public function handle($request, Closure $next)
    {
        // 1. 如果当前请求需要跨域处理，则注册"脚本终止回调"
        if ($this->needCorsHandle($request)) {
            $this->registerShutdownCallback($request);
        }

        // 2. 非跨域请求直接放行
        if (!$this->needCorsHandle($request)) {
            return $next($request);
        }

        // 3. 处理预检请求（OPTIONS）
        if ($this->isPreflightRequest($request)) {
            $response = response('', 204); // 预检请求返回204无内容

            return $this->addCorsHeaders($response, $request);
        }

        // 4. 正常请求：先执行后续逻辑，再添加跨域头
        $response = $next($request);

        return $this->addCorsHeaders($response, $request);
    }

    /**
     * 注册脚本终止回调：解决dd()/die()导致跨域头丢失的问题
     */
    protected function registerShutdownCallback(Request $request)
    {
        register_shutdown_function(function () use ($request) {
            // 如果响应头还没发送，则强制添加跨域头
            if (!headers_sent()) {
                // 1. 捕获输出缓冲（如dd()的调试内容）
                $output = ob_get_clean() ?: '';

                // 2. 生成跨域头
                $corsHeaders = $this->buildCorsHeaders($request);

                // 3. 发送跨域头
                foreach ($corsHeaders as $key => $value) {
                    header("{$key}: {$value}");
                }

                // 4. 输出缓冲内容（确保调试信息能显示）
                echo $output;
            }
        });
    }

    /**
     * 判断当前请求是否需要跨域处理
     */
    protected function needCorsHandle(Request $request): bool
    {
        return $this->isCorsRequest($request) && $this->hasMatchingPath($request);
    }

    /**
     * 构建跨域响应头（统一生成逻辑）
     */
    protected function buildCorsHeaders(Request $request): array
    {
        $headers = [];

        // 1. 允许的源（Access-Control-Allow-Origin）
        if ($this->allowAllOrigins && !$this->supportsCredentials) {
            $headers['Access-Control-Allow-Origin'] = '*';
        } elseif ($this->isSingleOriginAllowed()) {
            $headers['Access-Control-Allow-Origin'] = $this->allowedOrigins[0];
        } elseif ($this->isOriginAllowed($request)) {
            $headers['Access-Control-Allow-Origin'] = $request->header('Origin');
        }

        // 2. 允许携带凭证（如cookie）
        if ($this->supportsCredentials) {
            $headers['Access-Control-Allow-Credentials'] = 'true';
        }

        // 3. 允许的方法
        $headers['Access-Control-Allow-Methods'] = $this->allowAllMethods
            ? strtoupper($request->header('Access-Control-Request-Method', ''))
            : implode(', ', $this->allowedMethods);

        // 4. 允许的请求头
        $headers['Access-Control-Allow-Headers'] = $this->allowAllHeaders
            ? $request->header('Access-Control-Request-Headers', '')
            : implode(', ', $this->allowedHeaders);

        // 5. 预检请求缓存时间
        if ($this->maxAge > 0) {
            $headers['Access-Control-Max-Age'] = (string)$this->maxAge;
        }

        // 6. 允许暴露的响应头（前端可读取）
        if (!empty($this->exposedHeaders)) {
            $headers['Access-Control-Expose-Headers'] = implode(', ', $this->exposedHeaders);
        }

        return $headers;
    }

    /**
     * 给响应对象添加跨域头
     */
    protected function addCorsHeaders(Response $response, Request $request): Response
    {
        $corsHeaders = $this->buildCorsHeaders($request);
        foreach ($corsHeaders as $key => $value) {
            $response->header([$key => $value]);
        }
        return $response;
    }

    // ---------------- 以下是辅助判断方法（逻辑清晰分离） ----------------

    /**
     * 是否为跨域请求（有Origin头）
     */
    protected function isCorsRequest(Request $request): bool
    {
        return !empty($request->header('Origin'));
    }

    /**
     * 是否为预检请求（OPTIONS方法+带预检头）
     */
    protected function isPreflightRequest(Request $request): bool
    {
        return $request->method() === 'OPTIONS' && !empty($request->header('Access-Control-Request-Method'));
    }

    /**
     * 判断请求路径是否在跨域配置的路径中
     */
    protected function hasMatchingPath(Request $request): bool
    {
        $currentUrl = trim($request->baseUrl(), '/') ?: '/';
        $paths = $this->getPathsByHost($request->host(true));

        foreach ($paths as $path) {
            $path = trim($path, '/') ?: '/';
            if ($path === $currentUrl) {
                return true;
            }
            if (preg_match($this->wildcardToRegex($path), $currentUrl)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断源是否允许访问
     */
    protected function isOriginAllowed(Request $request): bool
    {
        $origin = $request->header('Origin');
        if ($this->allowAllOrigins) {
            return true;
        }
        if (in_array($origin, $this->allowedOrigins)) {
            return true;
        }
        foreach ($this->allowedOriginsPatterns as $pattern) {
            if (preg_match($pattern, $origin)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否只允许单个源（简化判断）
     */
    protected function isSingleOriginAllowed(): bool
    {
        return !$this->allowAllOrigins && empty($this->allowedOriginsPatterns) && count($this->allowedOrigins) === 1;
    }

    /**
     * 根据主机名获取对应的跨域路径配置
     */
    protected function getPathsByHost(string $host): array
    {
        return $this->paths[$host] ?? array_filter($this->paths, fn($p) => is_string($p));
    }

    /**
     * 通配符转为正则（如 "*.example.com" 转为正则）
     */
    protected function wildcardToRegex(string $pattern): string
    {
        return '#^' . str_replace('\*', '.*', preg_quote($pattern, '#')) . '$#u';
    }
}